﻿namespace MicroCloud.Api.Identity.Controllers
{
    /// <summary>
    /// 网站-认证
    /// </summary>
    [Description("身份认证")]
    [ModuleInfo(Code = "Identity", Name = "身份认证", OrderNo = 1)]
    public class IdentityController : IdentityControllerBase
    {
        #region "属性"
        #region "获取 身份认证模块业务契约"
        /// <summary>
        /// 获取 身份认证模块业务契约
        /// </summary>
        protected IIdentityContract IdentityContract => ServiceProvider.GetService<IIdentityContract>();
        #endregion
        #region "获取 登录管理器"
        /// <summary>
        /// 获取 登录管理器
        /// </summary>
        protected SignInManager<User> SignInManager => ServiceProvider.GetService<SignInManager<User>>();
        #endregion
        #region "获取 用户管理器"
        /// <summary>
        /// 获取 用户管理器
        /// </summary>
        protected UserManager<User> UserManager => ServiceProvider.GetService<UserManager<User>>();
        #endregion
        #region "获取 验证码处理功能"
        /// <summary>
        /// 获取 验证码处理功能
        /// </summary>
        protected IVerifyCodeService VerifyCodeService => ServiceProvider.GetService<IVerifyCodeService>();
        #endregion
        #region "获取 用户Claims提供器"
        /// <summary>
        /// 获取 用户Claims提供器
        /// </summary>
        protected IUserClaimsProvider UserClaimsProvider => ServiceProvider.GetService<IUserClaimsProvider>();
        #endregion
        #region "获取 微云缓存服务功能"
        /// <summary>
        /// 获取 微云缓存服务功能
        /// </summary>
        protected ICacheDataService MicroCloudCacheService => ServiceProvider.GetService<ICacheDataService>();
        #endregion

        #region "获取 用户仓储模型"
        /// <summary>
        /// 获取 用户仓储模型
        /// </summary>
        protected IRepository<User, long> UserRepository => ServiceProvider.GetService<IRepository<User, long>>();
        #endregion
        #region "获取 用户证照仓储模型"
        /// <summary>
        /// 获取 用户证照仓储模型
        /// </summary>
        protected IRepository<UserPortfolio, long> UserPortfolioRepository => ServiceProvider.GetService<IRepository<UserPortfolio, long>>();
        #endregion
        #region "获取 用户详情仓储模型"
        /// <summary>
        /// 获取 用户详情仓储模型
        /// </summary>
        protected IRepository<UserDetail, long> UserDetailRepository => ServiceProvider.GetService<IRepository<UserDetail, long>>();
        #endregion

        #endregion

        #region "Action方法"
        #region "用户名是否存在"
        /// <summary>
        /// 用户名是否存在
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="ignoreSelf">忽略自己</param>
        /// <returns>是否存在</returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("用户名是否存在")]
        public virtual bool CheckUserNameExists(string userName, bool ignoreSelf = true)
        {
            bool exists = false;
            if (userName.IsMissing())
            {
                return exists;
            }
            if (User.Identity?.IsAuthenticated != true)
            {
                exists = UserManager.Users.Any(m => m.NormalizedUserName == UserManager.NormalizeName(userName));
            }
            else
            {
                if (ignoreSelf)
                {
                    long userId = User.Identity.GetUserId<long>();
                    exists = UserManager.Users.Any(m => m.NormalizedUserName == UserManager.NormalizeName(userName) && !m.Id.Equals(userId));
                }
                else
                {
                    exists = UserManager.Users.Any(m => m.NormalizedUserName == UserManager.NormalizeName(userName));
                }
            }

            return exists;
        }
        #endregion
        #region "用户Email是否存在"
        /// <summary>
        /// 用户Email是否存在
        /// </summary>
        /// <param name="email">电子邮箱</param>
        /// <param name="ignoreSelf">忽略自己</param>
        /// <returns>是否存在</returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("用户Email是否存在")]
        public virtual bool CheckEmailExists(string email, bool ignoreSelf = true)
        {
            bool exists = false;
            if (email.IsNullOrEmpty())
            {
                return false;
            }
            if (User.Identity?.IsAuthenticated != true)
            {
                exists = UserManager.Users.Any(m => m.NormalizeEmail == UserManager.NormalizeEmail(email));
            }
            else
            {
                if (ignoreSelf)
                {
                    long userId = User.Identity.GetUserId<long>();
                    exists = UserManager.Users.Any(m => m.NormalizeEmail == UserManager.NormalizeEmail(email) && !m.Id.Equals(userId));
                }
                else
                {
                    exists = UserManager.Users.Any(m => m.NormalizeEmail == UserManager.NormalizeEmail(email));
                }
            }
            return exists;
        }
        #endregion
        #region "用户手机号是否存在"
        /// <summary>
        /// 用户手机号是否存在
        /// </summary>
        /// <param name="phoneNumber">手机号</param>
        /// <param name="ignoreSelf">忽略自己</param>
        /// <returns>是否存在</returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("用户手机号是否存在")]
        public virtual bool CheckPhoneNumberExists(string phoneNumber, bool ignoreSelf = true)
        {
            bool exists = false;
            if (phoneNumber.IsNullOrEmpty())
            {
                return false;
            }
            if (User.Identity?.IsAuthenticated != true)
            {
                exists = UserManager.Users.Any(m => m.PhoneNumber == phoneNumber);
            }
            else
            {
                if (ignoreSelf)
                {
                    long userId = User.Identity.GetUserId<long>();
                    exists = UserManager.Users.Any(m => m.PhoneNumber == phoneNumber && !m.Id.Equals(userId));
                }
                else
                {
                    exists = UserManager.Users.Any(m => m.PhoneNumber == phoneNumber);
                }
            }
            return exists;
        }
        #endregion
        #region "用户昵称是否存在"
        /// <summary>
        /// 用户昵称是否存在
        /// </summary>
        /// <param name="nickName">用户昵称</param>
        /// <param name="ignoreSelf">忽略自己</param>
        /// <returns>是否存在</returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("用户昵称是否存在")]
        public virtual async Task<bool> CheckNickNameExists(string nickName, bool ignoreSelf = true)
        {
            IdentityResult result = IdentityResult.Success;
            if (nickName.IsNullOrEmpty())
            {
                return false;
            }

            IUserValidator<User> nickNameValidator =
                UserManager.UserValidators.FirstOrDefault(m => m.GetType() == typeof(UserNickNameValidator<User, long>));
            if (nickNameValidator == null)
            {
                return false;
            }

            if (User.Identity?.IsAuthenticated != true)
            {
                result = await nickNameValidator.ValidateAsync(UserManager, new User() { NickName = nickName });
            }
            else
            {
                if (ignoreSelf)
                {
                    long userId = User.Identity.GetUserId<long>();
                    result = await nickNameValidator.ValidateAsync(UserManager, new User() { Id = userId, NickName = nickName });
                }
                else
                {
                    result = await nickNameValidator.ValidateAsync(UserManager, new User() { NickName = nickName });
                }
            }
            return !result.Succeeded;
        }
        #endregion

        #region "用户注册"
        /// <summary>
        /// 用户注册
        /// </summary>
        /// <param name="dto">注册信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [ModuleInfo]
        [AllowAnonymous]
        [Description("注册")]
        public virtual async Task<AjaxResult> Register(RegisterDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));
            Check.NotNullOrEmpty(dto.UserName, nameof(dto.UserName));

            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            if (!VerifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            {
                return AjaxResult.Error(I18N.T("验证码错误"));
            }

            //设置注册信息
            UserRegisterType registerType = UserRegisterType.Neither;
            bool emailConfirmed = false, phoneNumberConfirmed = false;
            if (!dto.Email.IsNullOrEmpty() && !dto.PhoneNumber.IsNullOrEmpty())
            {
                registerType = UserRegisterType.Both;
            }
            else if (!dto.Email.IsNullOrEmpty() && dto.PhoneNumber.IsNullOrEmpty())
            {
                registerType = UserRegisterType.Email;
            }
            else if (dto.Email.IsNullOrEmpty() && !dto.PhoneNumber.IsNullOrEmpty())
            {
                registerType = UserRegisterType.PhoneNumber;
            }
            //dto.UserName = dto.Email;
            dto.NickName = !dto.NickName.IsMissing() ? dto.NickName : $"User_{new Random().NextLetterAndNumberString(8)}"; //随机用户昵称
            dto.RegisterIp = HttpContext.GetClientIp();

            //验证手机短信验证码
            if ((registerType == UserRegisterType.Both || registerType == UserRegisterType.PhoneNumber)
                && (!dto.SmsCode.IsMissing() && !dto.SmsCodeId.IsMissing()))
            {

                var operatResult = await CheckSmsCode(dto.SmsCodeId, dto.SmsCode, dto.PhoneNumber);
                if (!operatResult.Succeeded)
                {
                    return operatResult.ToAjaxResult(true);
                }
                phoneNumberConfirmed = true;
            }

            //注册用户信息
            OperationResult<User> result = await IdentityContract.Register(registerType, dto, emailConfirmed, phoneNumberConfirmed);

            if (result.Succeeded)
            {
                if (registerType == UserRegisterType.Both || registerType == UserRegisterType.Email)
                {
                    User user = result.Data;

                    var sendEmailResult = await SendActivationEmail(user, dto.EmailConfirmUrl);
                    if (!sendEmailResult.Succeeded)
                    {
                        return sendEmailResult;
                    }
                    //return result.ToAjaxResult(m => new { m.UserName, m.NickName, m.Email });
                }
                return result.ToAjaxResult(m => new { m.UserName, m.NickName, m.Email, m.PhoneNumber });
            }
            return result.ToAjaxResult();
        }
        #endregion

        #region "用户登录-暂不开放接口"
        /// <summary>
        /// 用户登录-暂不开放接口
        /// </summary>
        /// <param name="dto">登录信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [AllowAnonymous]
        [Description("登录")]
        [NonAction]
        [NonFunction]
        public virtual async Task<AjaxResult> Login(LoginDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            //todo: 校验验证码

            dto.Ip = HttpContext.GetClientIp();
            dto.UserAgent = Request.Headers["User-Agent"].FirstOrDefault();

            IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
            OperationResult<User> result = await IdentityContract.Login(dto);

            if (!result.Succeeded)
            {
                return result.ToAjaxResult();
            }

            User user = result.Data;
            await SignInManager.SignInAsync(user, dto.Remember);

            await unitOfWork.CommitAsync();

            return AjaxResult.Success();
        }
        #endregion
        #region "获取身份认证令牌"
        /// <summary>
        /// 获取身份认证令牌
        /// </summary>
        /// <param name="dto">令牌请求DTO</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("获取令牌")]
        public virtual async Task<AjaxResult> Token(TokenDto dto)
        {
            string grantType = dto.GrantType?.UpperToLowerAndSplit("_");
            if (grantType == GrantTypeConstants.Password)
            {
                Check.NotNull(dto.Account, nameof(dto.Account));
                Check.NotNull(dto.Password, nameof(dto.Password));

                LoginDto loginDto = new()
                {
                    Account = dto.Account,
                    Password = dto.Password,
                    OrganizationId = dto.OrganizationId,
                    ClientType = dto.ClientType,
                    ClientId = dto.ClientId,
                    IsToken = true,
                    Ip = HttpContext.GetClientIp(),
                    UserAgent = Request.Headers["User-Agent"].FirstOrDefault()
                };

                IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
                OperationResult<User> result = await IdentityContract.Login(loginDto);

                if (!result.Succeeded)
                {
                    await unitOfWork.CommitAsync();
                    return result.ToAjaxResult();
                }

                User user = result.Data;
                JsonWebToken token = await CreateJwtToken(user, dto.OrganizationId, dto.ClientId, dto.ClientType);

                await unitOfWork.CommitAsync();
                return AjaxResult.Success(token);
            }

            if (grantType == GrantTypeConstants.RefreshToken)
            {
                Check.NotNull(dto.RefreshToken, nameof(dto.RefreshToken));
                try
                {
                    JsonWebToken token = await RefreshJwtToken(dto.RefreshToken);
                    return AjaxResult.Success(token);
                }
                catch (System.Exception ex)
                {
                    return AjaxResult.Error(ex.Message);
                }
            }

            return AjaxResult.Error(I18N.T("请指定授权类型[GrantType]为 {0} 或者 {1}", GrantTypeConstants.Password, GrantTypeConstants.RefreshToken), I18N.T("授权类型错误"));
        }
        #endregion
        #region "使用手机号获取身份认证令牌"
        /// <summary>
        /// 使用手机号获取身份认证令牌
        /// </summary>
        /// <param name="dto">手机号令牌请求DTO</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("使用手机号获取令牌")]
        public virtual async Task<AjaxResult> PhoneToken(PhoneTokenDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));
            Check.NotNullOrEmpty(dto.PhoneNumber, nameof(dto.PhoneNumber));
            Check.NotNullOrEmpty(dto.SmsCode, nameof(dto.SmsCode));
            Check.NotNullOrEmpty(dto.SmsCodeId, nameof(dto.SmsCodeId));

            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            var operatResult = await CheckSmsCode(dto.SmsCodeId, dto.SmsCode, dto.PhoneNumber);
            if (!operatResult.Succeeded)
            {
                return operatResult.ToAjaxResult(true);
            }

            LoginDto loginDto = new()
            {
                Account = dto.PhoneNumber,
                Password = "",
                Ip = HttpContext.GetClientIp(),
                UserAgent = Request.Headers["User-Agent"].FirstOrDefault()
            };

            IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
            OperationResult<User> result = await IdentityContract.PhoneLogin(loginDto);

            if (!result.Succeeded)
            {
                // 登录不成功，将用户信息返回前端，让用户选择绑定现有账号还是创建新账号
                return result.ToAjaxResult();
            }
            User user = result.Data;
            JsonWebToken token = await CreateJwtToken(user, dto.OrganizationId, dto.ClientId, dto.ClientType);

            await unitOfWork.CommitAsync();
            return AjaxResult.Success(token);
        }
        #endregion
        #region "使用授权码获取身份认证令牌"
        /// <summary>
        /// 使用授权码获取身份认证令牌
        /// </summary>
        /// <param name="dto">授权码身份认证令牌请求DTO</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("使用授权码令牌")]
        public virtual async Task<AjaxResult> AuthorizationToken(AuthorizationTokenDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));
            Check.NotNullOrEmpty(dto.Account, nameof(dto.Account));
            Check.NotNullOrEmpty(dto.AuthCode, nameof(dto.AuthCode));

            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }

            LoginDto loginDto = new()
            {
                Account = dto.Account,
                Password = dto.AuthCode,
                Ip = HttpContext.GetClientIp(),
                UserAgent = Request.Headers["User-Agent"].FirstOrDefault()
            };

            IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
            OperationResult<User> result = await IdentityContract.AuthorizationLogin(loginDto);

            if (!result.Succeeded)
            {
                // 登录不成功，将用户信息返回前端，让用户选择绑定现有账号还是创建新账号
                return result.ToAjaxResult();
            }
            User user = result.Data;
            JsonWebToken token = await CreateJwtToken(user, dto.OrganizationId, dto.ClientId, dto.ClientType);

            await unitOfWork.CommitAsync();
            return AjaxResult.Success(token);
        }
        #endregion

        #region "获取登录二维码令牌"
        /// <summary>
        /// 获取登录二维码令牌
        /// </summary>
        /// <param name="dto">二维码令牌DTO</param>
        /// <returns></returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("获取二维码令牌")]
        public virtual async Task<AjaxResult> GetQrCodeToken(QRCoderTokenDto dto)
        {
            var logoImage = dto.LogoImage;
            dto.LogoImage = "";
            var codeToken = await GetOrCreateQrCodeToken(dto, logoImage);

            return AjaxResult.Success(codeToken);
        }
        #endregion
        #region "扫描登录二维码令牌"
        /// <summary>
        /// 扫描登录二维码令牌
        /// </summary>
        /// <param name="dto">二维码令牌DTO</param>
        /// <returns>业务操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("扫描二维码令牌")]
        public virtual async Task<AjaxResult> ScanQrCodeToken(QRCoderTokenDto dto)
        {
            return await SetQrCodeTokenScan(dto);
        }
        #endregion
        #region "确认登录二维码令牌"
        /// <summary>
        /// 确认登录二维码令牌
        /// </summary>
        /// <param name="dto">二维码令牌DTO</param>
        /// <returns>业务操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("确认二维码令牌")]
        public virtual async Task<AjaxResult> ConfirmQrCodeToken(QRCoderTokenDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));
            var result = await SetQrCodeTokenScan(dto);
            if (!result.Succeeded)
            {
                return result;
            }
            if (dto.ProviderKey.IsMissing())
            {
                return AjaxResult.Error(I18N.T("无效的提供者秘钥信息"));
            }
            User user = await UserManager.FindByIdAsync(dto.ProviderKey);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("无效的提供者秘钥信息"));
            }
            if (user.IsLocked)
            {
                return AjaxResult.Error(I18N.T("用户已锁定"));
            }
            JsonWebToken token = await CreateJwtToken(user, dto.OrganizationId, dto.ClientId, dto.ClientType);
            //设置缓存
            string key = $"Identity_QrCodeToken_{dto.Id}";
            var group = $"Identity_QrCodeToken_{dto.ClientId}";
            DateTime now = DateTime.UtcNow;
            DateTime expiresUtc = now.AddSeconds(60 * 1);
            await MicroCloudCacheService.SetAsync(key, token, typeof(JsonWebToken), group, expiresUtc);

            return AjaxResult.Success();
        }
        #endregion

        #region "OAuth2登录"
        /// <summary>
        /// OAuth2登录
        /// </summary>
        /// <param name="provider">登录提供程序</param>
        /// <param name="returnUrl">登录成功返回URL</param>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("OAuth2登录")]
        public virtual IActionResult OAuth2(string provider, string returnUrl = null)
        {
            string redirectUrl = Url.Action(nameof(OAuth2Callback), "Identity", new { returnUrl });
            AuthenticationProperties properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
            return Challenge(properties, provider);
        }
        #endregion
        #region "OAuth2登录回调"
        /// <summary>
        /// OAuth2登录回调
        /// </summary>
        /// <param name="returnUrl">登录成功返回URL</param>
        /// <param name="remoteError">远程错误信息</param>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("OAuth2登录回调")]
        public virtual async Task<ActionResult> OAuth2Callback(string returnUrl = null, string remoteError = null)
        {
            if (remoteError != null)
            {
                Logger.LogError("第三方登录错误：{remoteError}", remoteError);
                throw new System.Exception(I18N.T("第三方登录错误：{0}", remoteError));
            }

            string url;
            ExternalLoginInfo info = await SignInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                url = "/#/exception/500";
                Logger.LogError("第三方登录返回的用户信息为空");
                return Redirect(url);
            }

            UserLoginInfoEx loginInfo = info.ToUserLoginInfoEx();
            var result = await IdentityContract.LoginOAuth2(loginInfo);
            // 登录不成功，将用户信息返回前端，让用户选择绑定现有账号还是创建新账号
            if (!result.Succeeded)
            {
                if (result.ResultType == OperationResultType.Forbidden)
                {
                    var errMsg = $"第三方登录错误：{result.Message}";
                    var loginInfoMsg = loginInfo.ToJsonString();
                    Logger.LogError("{errMsg}：{loginInfoMsg}", errMsg, loginInfoMsg);
                    throw new Exception(I18N.T("第三方登录错误：{0}", errMsg));
                }

                string cacheId = (string)result.Data;
                loginInfo.ProviderKey = cacheId;
                url = $"/#/oauth-callback?type={loginInfo.LoginProvider}&id={cacheId}&name={loginInfo.ProviderDisplayName?.AsUrlEncode()}&avatar={loginInfo.AvatarUrl?.AsUrlEncode()}";
                return Redirect(url);
            }

            var userName = info.Principal.Identity.Name;
            var providerName = info.ProviderDisplayName;
            Logger.LogDebug("用户 {userName} 通过 {providerName} OAuth2 登录成功", userName, providerName);
            JsonWebToken token = await CreateJwtToken((User)result.Data);
            url = $"/#/oauth-callback?token={token.ToJsonString()}";
            return Redirect(url);
        }
        #endregion
        #region "第三方身份获取认证令牌"
        /// <summary>
        /// 第三方身份获取认证令牌
        /// </summary>
        /// <param name="dto">第三方身份获取TokenDTO</param>
        /// <returns></returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("第三方身份获取令牌")]
        public virtual async Task<AjaxResult> OAuth2Token(OAuth2TokenDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));

            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            //获取第三方用户信息
            var result1 = await IdentityContract.CreateTicketAsync(dto);
            if (!result1.Succeeded)
            {
                return result1.ToAjaxResult();
            }

            UserLoginInfoEx loginInfo = result1.Data;
            var result2 = await IdentityContract.LoginOAuth2(loginInfo);
            // 登录不成功，将用户信息返回前端，让用户选择绑定现有账号还是创建新账号
            if (!result2.Succeeded)
            {
                if (result2.ResultType == OperationResultType.Forbidden)
                {
                    return result2.ToAjaxResult();
                }
                string cacheId = (string)result2.Data;
                loginInfo.ProviderKey = cacheId;

                return AjaxResult.Error(loginInfo, I18N.T("第三方登录失败：未找到已绑定用户信息或用户已锁定。"));
            }

            var user = (User)result2.Data;
            Logger.LogDebug("用户 {userName} 通过 {providerName} OAuth2登录成功", user.UserName, loginInfo.ProviderDisplayName);
            JsonWebToken token = await CreateJwtToken(user);
            return AjaxResult.Success(token);
        }
        #endregion
        #region "读取OAuth2登录信息"
        /// <summary>
        /// 读取OAuth2登录信息
        /// </summary>
        /// <param name="filterService">过滤表达式服务</param>
        /// <param name="request">分页请求</param>
        /// <returns>OAuth2登录信息页数据</returns>
        [HttpPost]
        [NonFunction]
        [Description("读取OAuth2信息")]
        public virtual PageData<UserLoginOutputDto> ReadOAuth2([FromServices] IFilterService filterService, PageRequest request)
        {
            Check.NotNull(request, nameof(request));
            IFunction function = this.GetExecuteFunction();

            long userId = User.Identity.GetUserId<long>();
            request.FilterGroup.AddRule("UserId", userId);
            request.AddDefaultSortCondition(new SortCondition("LoginProvider"));

            Expression<Func<UserLogin, bool>> exp = filterService.GetExpression<UserLogin>(request.FilterGroup);
            //var page = IdentityContract.UserLogins.ToPage<UserLogin, UserLoginOutputDto>(exp, request.PageCondition);
            var page = CacheService.ToPageCache<UserLogin, UserLoginOutputDto>(IdentityContract.UserLogins, exp, request.PageCondition, function);
            return page.ToPageData();
        }
        #endregion
        #region "登录并绑定账号"
        /// <summary>
        /// 登录并绑定账号
        /// </summary>
        /// <param name="loginInfo">第三方用户登录信息</param>
        /// <returns></returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("登录并绑定账号")]
        public virtual async Task<AjaxResult> LoginBind(UserLoginInfoEx loginInfo)
        {
            loginInfo.RegisterIp = HttpContext.GetClientIp();

            IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
            OperationResult<User> result = await IdentityContract.LoginBind(loginInfo);
            await unitOfWork.CommitAsync();
            if (!result.Succeeded)
            {
                return result.ToAjaxResult();
            }

            User user = result.Data;
            JsonWebToken token = await CreateJwtToken(user);
            return AjaxResult.Success(token);
        }
        #endregion
        #region "使用第三方账号一键登录"
        /// <summary>
        /// 使用第三方账号一键登录
        /// </summary>
        /// <param name="loginInfo">第三方用户登录信息</param>
        /// <returns></returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("第三方一键登录")]
        public virtual async Task<AjaxResult> LoginOneKey(UserLoginInfoEx loginInfo)
        {
            loginInfo.RegisterIp = HttpContext.GetClientIp();

            IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
            OperationResult<User> result = await IdentityContract.LoginOneKey(loginInfo);
            await unitOfWork.CommitAsync();
            if (!result.Succeeded)
            {
                return result.ToAjaxResult();
            }

            User user = result.Data;
            JsonWebToken token = await CreateJwtToken(user);

            return AjaxResult.Success(token);
        }
        #endregion
        #region "OAuth2绑定"
        /// <summary>
        /// OAuth2绑定
        /// </summary>
        /// <param name="provider">登录提供程序</param>
        /// <param name="returnUrl">登录成功返回URL</param>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("OAuth2绑定")]
        public virtual IActionResult OAuth2Bind(string provider, string returnUrl = null)
        {
            string redirectUrl = Url.Action(nameof(OAuth2BindCallback), "Identity", new { returnUrl });
            AuthenticationProperties properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
            return Challenge(properties, provider);
        }
        #endregion
        #region "OAuth2绑定回调"
        /// <summary>
        /// OAuth2绑定回调
        /// </summary>
        /// <param name="returnUrl">登录成功返回URL</param>
        /// <param name="remoteError">远程错误信息</param>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("OAuth2绑定回调")]
        public virtual async Task<ActionResult> OAuth2BindCallback(string returnUrl = null, string remoteError = null)
        {
            if (remoteError != null)
            {
                Logger.LogError("第三方绑定错误：{remoteError}", remoteError);
                throw new System.Exception(I18N.T("第三方绑定错误：{0}", remoteError));
            }

            string url;
            ExternalLoginInfo info = await SignInManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                url = "/#/exception/500";
                Logger.LogError("第三方绑定返回的用户信息为空");
                return Redirect(url);
            }

            UserLoginInfoEx loginInfo = info.ToUserLoginInfoEx();
            var result = await IdentityContract.BindOAuth2Preset(loginInfo);
            string cacheId = (string)result.Data;
            loginInfo.ProviderKey = cacheId;

            url = $"/#/dashboard/account/setting??type={loginInfo.LoginProvider}&id={cacheId}&name={loginInfo.ProviderDisplayName?.AsUrlEncode()}&avatar={loginInfo.AvatarUrl?.AsUrlEncode()}";
            return Redirect(url);
        }
        #endregion
        #region "OAuth2绑定提交"
        /// <summary>
        /// OAuth2绑定提交
        /// </summary>
        /// <param name="loginInfo">第三方用户登录信息</param>
        /// <returns></returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("OAuth2绑定提交")]
        public virtual async Task<AjaxResult> OAuth2BindCommit(UserLoginInfoEx loginInfo)
        {
            Check.NotNull(loginInfo, nameof(loginInfo));

            loginInfo.RegisterIp = HttpContext.GetClientIp();

            var result = await IdentityContract.BindOAuth2(loginInfo);
            return result.ToAjaxResult();
        }
        #endregion
        #region "解除第三方登录"
        /// <summary>
        /// 解除第三方登录
        /// </summary>
        /// <param name="ids">第三方用户登录主键</param>
        /// <returns></returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("解除第三方登录")]
        public virtual async Task<AjaxResult> RemoveOAuth2(long[] ids)
        {
            OperationResult result = await IdentityContract.DeleteUserLogins(ids);
            return result.ToAjaxResult();
        }
        #endregion

        #region "验证登录"
        /// <summary>
        /// 验证登录
        /// </summary>
        /// <param name="dto">登录信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("验证登录")]
        public virtual async Task<AjaxResult> CheckLogin(LoginDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            if (User.Identity?.IsAuthenticated == true)
            {
                dto.Account = User.Identity.Name;
            }

            IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork(true);
            OperationResult<User> result = await IdentityContract.CheckLogin(dto);
            await unitOfWork.CommitAsync();
            if (!result.Succeeded)
            {
                return result.ToAjaxResult();
            }
            return AjaxResult.Success();
        }
        #endregion
        #region "用户登出"
        /// <summary>
        /// 用户登出
        /// </summary>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("登出")]
        public virtual async Task<AjaxResult> Logout()
        {
            if (User.Identity?.IsAuthenticated != true)
            {
                return AjaxResult.Success(I18N.T("用户登出成功"));
            }

            long userId = User.Identity.GetUserId<long>();
            bool isToken = Request.Headers["Authorization"].Any(m => m.StartsWith("Bearer"));

            OperationResult result = await IdentityContract.Logout(isToken);
            return result.ToAjaxResult();
        }
        #endregion

        #region "获取在线用户信息"
        /// <summary>
        /// 获取在线用户信息
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [AllowAnonymous]
        [Description("在线用户信息")]
        public virtual async Task<ProfileDto> Profile()
        {
            if (User.Identity?.IsAuthenticated != true)
            {
                return null;
            }

            var onlineUserProvider = HttpContext.RequestServices.GetService<IOnlineUserProvider>();
            if (onlineUserProvider == null)
            {
                return null;
            }

            var userId = User.Identity.GetUserId();
            var onlineUser = await onlineUserProvider.GetOrCreate(userId);
            if (onlineUser == null)
            {
                return null;
            }
            onlineUser.RefreshTokens.Clear();
            //onlineUser.ExtendData.Clear();
            var organizationProvider = ServiceProvider.GetService<IOrganizationProvider>();
            var currentOrganization = organizationProvider.GetOrganizationById(CurrentOrganizationId).ToIdentity();
            var currentDepartment = organizationProvider.GetOrganizationById(CurrentDepartmentId).ToIdentity();
            var userEntity = UserRepository.QueryAsNoTracking(o => o.Portfolio, o => o.Detail).Where(o => o.Id == onlineUser.UserId.CastTo<long>()).FirstOrDefault();

            var user = new UserOutputDto(userEntity, userEntity.Detail);
            ProfileDto profileDto = new()
            {
                Key = onlineUser.Key,
                UserId = onlineUser.UserId,
                UserName = onlineUser.UserName,
                NickName = onlineUser.NickName,
                Email = onlineUser.Email,
                PhoneNumber = onlineUser.PhoneNumber,

                Avatar = userEntity.Portfolio.Avatar,
                Signature = userEntity.Portfolio.Signature,

                Gender = user.Gender,
                Birthday = user.Birthday,
                BloodType = user.BloodType,
                Tag = user.Tag,
                Title = user.Title,
                Address = user.Address,
                Profile = user.Profile,
                Sign = user.Sign,
                Remark = user.Remark,

                HomePath = user.HomePath,

                ChineseBirthday = user.ChineseBirthday,
                Age = user.Age,
                ZodiacSign = user.ZodiacSign,
                StarSign = user.StarSign,

                IsAdmin = onlineUser.IsAdmin,

                IsRefreshNext = onlineUser.IsRefreshNext,

                Roles = onlineUser.Roles,

                CurrentOrganization = currentOrganization,
                CurrentDepartment = currentDepartment,

                ExtendData = onlineUser.ExtendData,
            };

            //处理证照信息
            #region "处理证照信息"
            IFileService fileService = base.ServiceProvider.GetService<IFileService>();
            if (fileService != null)
            {
                var requestPath = fileService.GetRequestPath();
                if (!profileDto.Avatar.IsMissing())
                {
                    if (!profileDto.Avatar.ToLower().StartsWith("data:"))
                    {
                        if (!profileDto.Avatar.StartsWith('/'))
                        {
                            profileDto.Avatar = $"/{profileDto.Avatar}";
                        }
                        if (!requestPath.IsMissing())
                        {
                            if (requestPath.StartsWith('/'))
                            {
                                profileDto.Avatar = $"{requestPath}{profileDto.Avatar}";
                            }
                            else
                            {
                                profileDto.Avatar = $"{requestPath}{profileDto.Avatar}";
                            }
                        }
                    }
                }
                if (!profileDto.Signature.IsMissing())
                {
                    if (!profileDto.Signature.ToLower().StartsWith("data:"))
                    {
                        if (!profileDto.Signature.StartsWith('/'))
                        {
                            profileDto.Signature = $"/{profileDto.Signature}";
                        }
                        if (!requestPath.IsMissing())
                        {
                            if (requestPath.StartsWith('/'))
                            {
                                profileDto.Signature = $"{requestPath}{profileDto.Signature}";
                            }
                            else
                            {
                                profileDto.Signature = $"{requestPath}{profileDto.Signature}";
                            }
                        }
                    }
                }
            }
            #endregion

            return profileDto;
        }
        #endregion
        #region "编辑用户资料"
        /// <summary>
        /// 编辑用户资料
        /// </summary>
        /// <param name="dto">用户资料编辑DTO</param>
        /// <returns></returns>
        [HttpPost]
        [UnitOfWork]
        [ModuleInfo]
        [Description("编辑资料")]
        public virtual async Task<AjaxResult> ProfileEdit(ProfileEditDto dto)
        {
            long userId = User.Identity.GetUserId<long>();
            dto.Id = userId;
            User user = UserRepository.QueryAsNoTracking(o => o.Portfolio, o => o.Detail).Where(o => o.Id == userId).FirstOrDefault();
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", userId));
            }
            if (dto.UserName.IsMissing())
            {
                return AjaxResult.Error(I18N.T("用户名不能为空"));
            }
            if (dto.NickName.IsMissing())
            {
                return AjaxResult.Error(I18N.T("用户昵称不能为空"));
            }
            if (dto.UserName.ToLower() != user.UserName.ToLower())
            {
                bool exists = UserManager.Users.Any(m => m.NormalizedUserName == UserManager.NormalizeName(dto.UserName) && !m.Id.Equals(user.Id));
                if (exists)
                {
                    return AjaxResult.Error(I18N.T("用户名 {0} 已被注册", dto.UserName));
                }
            }
            if (dto.Email.IsMissing() || dto.Email.ToLower() != user.Email.ToLower())
            {
                user.EmailConfirmed = false;
                if (!dto.Email.IsMissing())
                {
                    bool exists = UserManager.Users.Any(m => m.NormalizeEmail == UserManager.NormalizeEmail(dto.Email) && !m.Id.Equals(user.Id));
                    if (exists)
                    {
                        return AjaxResult.Error(I18N.T("邮箱地址 {0} 已被注册", dto.Email));
                    }
                }
            }
            if (dto.PhoneNumber.IsMissing() || dto.PhoneNumber != user.PhoneNumber)
            {
                user.PhoneConfirmed = false;
                if (!dto.PhoneNumber.IsMissing())
                {
                    bool exists = UserManager.Users.Any(m => m.PhoneNumber == dto.PhoneNumber && !m.Id.Equals(user.Id));
                    if (exists)
                    {
                        return AjaxResult.Error(I18N.T("手机号码 {0} 已被注册", dto.PhoneNumber));
                    }
                }
            }

            user = dto.MapTo(user);
            var result = await UserManager.UpdateAsync(user);
            if (result.Succeeded)
            {
                //电子证照
                var userPortfolio = user.Portfolio ?? new UserPortfolio();
                userPortfolio.UserId = user.Id;
                userPortfolio.Avatar = dto.Avatar;
                userPortfolio.Signature = dto.Signature;
                var result2 = await UserPortfolioRepository.InsertOrUpdateAsync(new UserPortfolio[] { userPortfolio });
                if (!result.Succeeded)
                {
                    return result2.ToAjaxResult();
                }
                //用户详细信息
                var userDetail = user.Detail ?? new UserDetail();
                userDetail.UserId = user.Id;
                userDetail.Gender = dto.Gender;
                userDetail.Birthday = dto.Birthday;
                userDetail.BloodType = dto.BloodType;
                userDetail.Tag = dto.Tag.ExpandAndToString(",");
                userDetail.Title = dto.Title.ExpandAndToString(",");
                userDetail.Address = dto.Address;
                userDetail.Profile = dto.Profile;
                userDetail.Sign = dto.Sign;
                userDetail.Remark = dto.Remark;
                userDetail.HomePath = dto.HomePath;
                result2 = await UserDetailRepository.InsertOrUpdateAsync(new UserDetail[] { userDetail });
                if (!result.Succeeded)
                {
                    return result2.ToAjaxResult();
                }

                var onlineUserProvider = ServiceProvider.GetService<IOnlineUserProvider>();
                onlineUserProvider.Remove(user.Id.ToString());
            }

            return result.ToOperationResult().ToAjaxResult();
        }
        #endregion
        #region "修改密码"
        /// <summary>
        /// 修改密码
        /// </summary>
        /// <param name="dto">修改密码信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [ModuleInfo]
        [Description("修改密码")]
        public virtual async Task<AjaxResult> ChangePassword(ChangePasswordDto dto)
        {
            Check.NotNull(dto, nameof(dto));
            Check.NotNullOrEmpty(dto.NewPassword, nameof(dto.NewPassword));
            Check.NotNullOrEmpty(dto.ConfirmNewPassword, nameof(dto.ConfirmNewPassword));
            if (dto.NewPassword == dto.OldPassword)
            {
                return AjaxResult.Error(I18N.T("密码没有改变"));
            }
            if (dto.NewPassword != dto.ConfirmNewPassword)
            {
                return AjaxResult.Error(I18N.T("两次输入密码不一致"));
            }

            long userId = User.Identity.GetUserId<long>();
            User user = await UserManager.FindByIdAsync(userId.ToString());
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", userId));
            }

            IdentityResult result = string.IsNullOrEmpty(dto.OldPassword)
                ? await UserManager.AddPasswordAsync(user, dto.NewPassword)
                : await UserManager.ChangePasswordAsync(user, dto.OldPassword, dto.NewPassword);
            return result.ToOperationResult().ToAjaxResult();
        }
        #endregion

        #region "获取组织机构节点"
        /// <summary>
        /// 获取组织机构节点
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [Description("获取组织机构节点")]
        public virtual async Task<AjaxResult> GetOrganizationNode()
        {
            if (User.Identity?.IsAuthenticated != true)
            {
                return AjaxResult.Success(Array.Empty<OrganizationNode>());
            }

            var organizationProvider = ServiceProvider.GetService<IOrganizationProvider>();
            var dtos = await organizationProvider?.GetUserOrganizations(User.Identity.GetUserId());
            if (dtos == null)
            {
                return AjaxResult.Success(Array.Empty<OrganizationNode>());
            }
            var nodes = dtos.ToNodes();
            return AjaxResult.Success(nodes.ToArray());
        }
        #endregion
        #region "获取组织机构树"
        /// <summary>
        /// 获取组织机构树
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [NonFunction]
        [Description("获取组织机构树")]
        public virtual async Task<AjaxResult> GetOrganizationTree()
        {
            if (User.Identity?.IsAuthenticated != true)
            {
                return AjaxResult.Success(Array.Empty<OrganizationTreeNode>());
            }

            var organizationProvider = ServiceProvider.GetService<IOrganizationProvider>();
            var treeNodes = await organizationProvider?.GetUserOrganizationTreeNodes(User.Identity.GetUserId());
            if (treeNodes == null)
            {
                return AjaxResult.Success(Array.Empty<OrganizationTreeNode>());
            }
            return AjaxResult.Success(treeNodes);
        }
        #endregion
        #region "设置当前组织机构"
        /// <summary>
        /// 设置当前组织机构
        /// </summary>
        /// <param name="organizationId">组织机构编号</param>
        /// <returns></returns>
        [HttpPost]
        [NonFunction]
        [Description("设置当前组织机构")]
        public virtual async Task<AjaxResult> SetOrganization(string organizationId)
        {
            Check.NotNullOrEmpty(organizationId, nameof(organizationId));

            ClaimsIdentity identity = User.Identity as ClaimsIdentity;
            var userId = identity.GetUserId();
            var clientId = identity.GetClientId();
            var clientType = identity.GetClientType();
            RequestClientType eClientType = RequestClientType.Browser;
            var enumEntity = eClientType.ToList().FirstOrDefault(o => o.Name == clientType);
            eClientType = (RequestClientType)enumEntity.Value;

            var onlineUserProvider = ServiceProvider.GetService<IOnlineUserProvider>();
            onlineUserProvider.Remove(userId);

            IJwtBearerService jwtBearerService = ServiceProvider.GetService<IJwtBearerService>();
            JsonWebToken token = await jwtBearerService.CreateToken(userId, organizationId, clientId, eClientType);

            return AjaxResult.Success(token);
        }
        #endregion

        #region "发送激活注册邮件"
        /// <summary>
        /// 发送激活注册邮件
        /// </summary>
        /// <param name="dto">激活邮箱信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("发送激活注册邮件")]
        public virtual async Task<AjaxResult> SendConfirmEmail(SendEmailDto dto)
        {
            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            if (!VerifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            {
                return AjaxResult.Error(I18N.T("验证码错误"));
            }

            User user = await UserManager.FindByEmailAsync(dto.Email);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.Email));
            }
            if (user.EmailConfirmed)
            {
                return AjaxResult.Error(I18N.T("邮箱已激活"));
            }

            var sendEmailResult = await SendActivationEmail(user, dto.EmailConfirmUrl);
            return sendEmailResult;
        }
        #endregion
        #region "激活邮箱"
        /// <summary>
        /// 激活邮箱
        /// </summary>
        /// <param name="dto">电子邮箱</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("激活邮箱")]
        public virtual async Task<AjaxResult> ConfirmEmail(ConfirmEmailDto dto)
        {
            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }

            User user = await UserManager.FindByEmailAsync(dto.Email);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.Email));
            }
            if (user.EmailConfirmed)
            {
                return AjaxResult.Success(I18N.T("邮箱已激活"));
            }

            string code = UrlBase64ReplaceChar(dto.Code);
            IdentityResult result = await UserManager.ConfirmEmailAsync(user, code);
            return result.ToOperationResult().ToAjaxResult();
        }
        #endregion
        #region "发送重置密码邮件"
        /// <summary>
        /// 发送重置密码邮件
        /// </summary>
        /// <param name="dto">发送邮件信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [NonFunction]
        [AllowAnonymous]
        [Description("发送重置密码邮件")]
        public virtual async Task<AjaxResult> SendResetPwdEmail(SendEmailDto dto)
        {
            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            if (!VerifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            {
                return AjaxResult.Error(I18N.T("验证码错误"));
            }

            User user = await UserManager.FindByEmailAsync(dto.Email);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.Email));
            }
            if (!user.EmailConfirmed)
            {
                return AjaxResult.Error(I18N.T("请先激活电子邮箱"), I18N.T("邮箱未激活"));
            }

            return await SendResetPasswordEmail(user, dto.EmailConfirmUrl);
        }
        #endregion
        #region "根据电子邮箱重置登录密码"
        /// <summary>
        /// 根据电子邮箱重置登录密码
        /// </summary>
        /// <param name="dto">重置密码信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("根据电子邮箱重置登录密码")]
        public virtual async Task<AjaxResult> ResetPwdByEmail(ResetPwdEmailDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            User user = await UserManager.FindByEmailAsync(dto.Email);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.Email));
            }
            if (!user.EmailConfirmed)
            {
                return AjaxResult.Error(I18N.T("请先激活电子邮箱"), I18N.T("邮箱未激活"));
            }

            string token = UrlBase64ReplaceChar(dto.Token);
            IdentityResult result = await UserManager.ResetPasswordAsync(user, token, dto.NewPassword);

            return result.ToOperationResult().ToAjaxResult();
        }
        #endregion
        #region "用户邮箱变更"
        /// <summary>
        /// 用户邮箱变更
        /// </summary>
        /// <param name="dto">用户邮箱变更DTO</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [ModuleInfo]
        [Description("用户邮箱变更")]
        public virtual async Task<AjaxResult> ChangeUserEmail(EmailEditDto dto)
        {
            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            if (!VerifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            {
                return AjaxResult.Error(I18N.T("验证码错误"));
            }

            long userId = User.Identity.GetUserId<long>();
            dto.Id = userId;
            bool exists = UserManager.Users.Any(m => m.NormalizeEmail == UserManager.NormalizeEmail(dto.Email) && m.Id != userId);
            if (exists)
            {
                return AjaxResult.Error(I18N.T("邮箱地址 {0} 已被使用", dto.Email));
            }

            User user = await UserManager.FindByIdAsync(userId.ToString());
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.Email));
            }

            //修改邮箱
            user.Email = dto.Email;
            user.EmailConfirmed = false;

            var result = await UserManager.UpdateAsync(user);
            if (!result.Succeeded)
            {
                return result.ToOperationResult().ToAjaxResult();
            }

            var onlineUserProvider = ServiceProvider.GetService<IOnlineUserProvider>();
            onlineUserProvider.Remove(user.Id.ToString());

            var sendEmailResult = await SendActivationEmail(user, dto.EmailConfirmUrl);
            return sendEmailResult;
        }
        #endregion

        #region "激活手机"
        /// <summary>
        /// 激活手机
        /// </summary>
        /// <param name="dto">确认手机DTO</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("激活手机")]
        public virtual async Task<AjaxResult> ConfirmPhone(ConfirmPhoneDto dto)
        {
            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            //if (!_verifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            //{
            //    return new AjaxResult("验证码错误，请刷新重试", AjaxResultType.Error);
            //}

            User user = UserManager.Users.FirstOrDefault(o => o.PhoneNumber == dto.PhoneNumber);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.PhoneNumber));
            }
            if (user.PhoneConfirmed)
            {
                return AjaxResult.Success(I18N.T("手机已激活"));
            }

            var operatResult = await CheckSmsCode(dto.SmsCodeId, dto.SmsCode, dto.PhoneNumber);
            if (!operatResult.Succeeded)
            {
                return operatResult.ToAjaxResult(true);
            }
            user.PhoneConfirmed = true;

            var result = await UserManager.UpdateAsync(user);
            if (!result.Succeeded)
            {
                return result.ToOperationResult().ToAjaxResult();
            }
            return AjaxResult.Success(new { user.UserName, user.NickName, user.PhoneNumber });
        }
        #endregion
        #region "根据手机重置登录密码"
        /// <summary>
        /// 根据手机重置登录密码
        /// </summary>
        /// <param name="dto">重置密码信息</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [NonFunction]
        [AllowAnonymous]
        [Description("根据手机重置登录密码")]
        public virtual async Task<AjaxResult> ResetPwdByPhone(ResetPwdPhoneDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            //if (!_verifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            //{
            //    return new AjaxResult("验证码错误，请刷新重试", AjaxResultType.Error);
            //}

            User user = UserManager.Users.FirstOrDefault(o => o.PhoneNumber == dto.PhoneNumber);
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.PhoneNumber));
            }
            if (!user.PhoneConfirmed)
            {
                return AjaxResult.Error(I18N.T("请先激活手机"), I18N.T("手机未激活"));
            }

            //验证短信验证码
            var operatResult = await CheckSmsCode(dto.SmsCodeId, dto.SmsCode, dto.PhoneNumber);
            if (!operatResult.Succeeded)
            {
                return operatResult.ToAjaxResult(true);
            }

            //重置密码
            string token = await UserManager.GeneratePasswordResetTokenAsync(user);
            IdentityResult result = await UserManager.ResetPasswordAsync(user, token, dto.NewPassword);

            return result.ToOperationResult().ToAjaxResult();
        }
        #endregion
        #region "用户手机变更"
        /// <summary>
        /// 用户手机变更
        /// </summary>
        /// <param name="dto">用户手机变更DTO</param>
        /// <returns>JSON操作结果</returns>
        [HttpPost]
        [UnitOfWork]
        [ModuleInfo]
        [Description("用户手机变更")]
        public virtual async Task<AjaxResult> ChangeUserPhone(PhoneEditDto dto)
        {
            if (!ModelState.IsValid)
            {
                return AjaxResult.Error(I18N.T("提交信息验证失败"));
            }
            //if (!_verifyCodeService.CheckCode(dto.VerifyCode, dto.VerifyCodeId))
            //{
            //    return new AjaxResult("验证码错误，请刷新重试", AjaxResultType.Error);
            //}

            long userId = User.Identity.GetUserId<long>();
            dto.Id = userId;
            bool exists = UserManager.Users.Any(m => m.PhoneNumber == dto.PhoneNumber && m.Id != userId);
            if (exists)
            {
                return AjaxResult.Error(I18N.T("手机号码 {0} 已被使用", dto.PhoneNumber));
            }

            User user = await UserManager.FindByIdAsync(userId.ToString());
            if (user == null)
            {
                return AjaxResult.Error(I18N.T("用户 {0} 不存在", dto.PhoneNumber));
            }

            var operatResult = await CheckSmsCode(dto.SmsCodeId, dto.SmsCode, dto.PhoneNumber);
            if (!operatResult.Succeeded)
            {
                return operatResult.ToAjaxResult(true);
            }

            //修改手机
            user.PhoneNumber = dto.PhoneNumber;
            user.PhoneConfirmed = true;

            var result = await UserManager.UpdateAsync(user);
            if (!result.Succeeded)
            {
                return result.ToOperationResult().ToAjaxResult();
            }

            var onlineUserProvider = ServiceProvider.GetService<IOnlineUserProvider>();
            onlineUserProvider.Remove(user.Id.ToString());

            return AjaxResult.Success(new { user.UserName, user.NickName, user.PhoneNumber });
        }
        #endregion

        #endregion

        #region "受保护方法"
        #region "创建JwtToken"
        /// <summary>
        /// 创建JwtToken
        /// </summary>
        /// <param name="user">用户信息</param>
        /// <param name="organizationId">组织机构编号</param>
        /// <param name="clientId">客户端ID</param>
        /// <param name="clientType">请求的客户端类型</param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<JsonWebToken> CreateJwtToken(User user, string organizationId = "", string clientId = "", RequestClientType clientType = RequestClientType.Browser)
        {
            var jwtBearerService = ServiceProvider.GetRequiredService<IJwtBearerService>();
            var token = await jwtBearerService.CreateToken(user.Id.ToString(), organizationId, clientId, clientType);

            return token;
        }
        #endregion
        #region "刷新JwtToken"
        /// <summary>
        /// 刷新JwtToken
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<JsonWebToken> RefreshJwtToken(string refreshToken)
        {
            IJwtBearerService jwtBearerService = ServiceProvider.GetRequiredService<IJwtBearerService>();
            JsonWebToken token = await jwtBearerService.RefreshToken(refreshToken);
            return token;
        }
        #endregion

        #region "获取或创建二维码令牌信息"
        /// <summary>
        /// 获取或创建二维码令牌信息
        /// </summary>
        /// <param name="dto"></param>
        /// <param name="logoImage"></param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<object> GetOrCreateQrCodeToken(QRCoderTokenDto dto, string logoImage = "")
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));
            string key;
            if (!dto.Id.IsMissing())
            {
                key = $"Identity_QrCodeToken_{dto.Id}";
                //已存在直接返回
                var cacheData = await MicroCloudCacheService.GetAsync(key);
                if (cacheData != null)
                {
                    return cacheData.Value;
                }
            }
            //不存在创建后返回
            dto.Id = Guid.NewGuid().ToString("N").ToUpper();
            //dto.ClientId = dto.ClientId.IsMissing() ? Guid.NewGuid().ToString("N").ToUpper() : dto.ClientId;
            key = $"Identity_QrCodeToken_{dto.Id}";
            QRCodeContentDto codeContentDto = new()
            {
                Id = dto.Id,
                Type = "QrCodeToken",

                ClientType = dto.ClientType,
                ClientId = dto.ClientId,

                Data = null,
            };
            string qrcodeBase64String = "";
            if (!logoImage.IsMissing())
            {
                var logoImageBase64 = logoImage.RemoveBase64StringHeader();
                var logoImg = Image.Load(Convert.FromBase64String(logoImageBase64));
                logoImg.Mutate(ctx =>
                {
                    ctx.BackgroundColor(Color.White);
                });
                var qrImage = QRCoder.CreateImage(codeContentDto.ToJsonString(false, false), logoImg, 256, 256, 0.15, 0.15);
                qrcodeBase64String = qrImage.ToBase64String();
            }
            else
            {
                var qrImage = QRCoder.CreateImage(codeContentDto.ToJsonString(false, false), 256, 256);
                qrcodeBase64String = qrImage.ToBase64String();
            }
            var codeDto = new QRCodeDto
            {
                Content = codeContentDto,
                Image = qrcodeBase64String,
                IsScan = false,
            };
            var group = $"Identity_QrCodeToken_{dto.ClientId}";
            DateTime now = DateTime.UtcNow;
            DateTime expiresUtc = now.AddSeconds(60 * 1);
            await MicroCloudCacheService.SetAsync(key, codeDto, typeof(QRCodeDto), group, expiresUtc);
            return codeDto;
        }
        #endregion
        #region "设置二维码令牌信息扫描状态"
        /// <summary>
        /// 设置二维码令牌信息扫描状态
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<AjaxResult> SetQrCodeTokenScan(QRCoderTokenDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));

            if (dto.Id.IsMissing())
            {
                return AjaxResult.Error(I18N.T("二维码信息不存在或已失效"));
            }
            string key = $"Identity_QrCodeToken_{dto.Id}";
            var cacheData = await MicroCloudCacheService.GetAsync(key);
            if (cacheData == null)
            {
                return AjaxResult.Error(I18N.T("二维码信息不存在或已失效"));
            }
            if (cacheData.Value is JsonWebToken)
            {
                return AjaxResult.Error(I18N.T("二维码信息已验证"));
            }
            if (cacheData.Value is not QRCodeDto)
            {
                return AjaxResult.Error(I18N.T("二维码信息不存在或已失效"));
            }
            QRCodeDto codeDto = cacheData.Value as QRCodeDto;
            codeDto.IsScan = true;

            var cacheDataType = cacheData.Type;
            var cacheDataGroup = cacheData.Group;
            var expiresUtc = cacheData.ExpiresUtc;
            await MicroCloudCacheService.SetAsync(key, codeDto, cacheDataType, cacheDataGroup, expiresUtc);

            return AjaxResult.Success(codeDto);
        }
        #endregion

        #region "发送激活注册邮件"
        /// <summary>
        /// 发送激活注册邮件
        /// </summary>
        /// <param name="user">发送的用户</param>
        /// <param name="emailConfirmUrl">邮箱确认地址</param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<AjaxResult> SendActivationEmail(User user, string emailConfirmUrl)
        {
            var subjectPrefix = ServiceProvider.GetMicroCloudOptions()?.Email?.Sender?.SubjectPrefix.ReplaceNull();

            emailConfirmUrl = emailConfirmUrl.IsUrl() ? emailConfirmUrl.TrimEnd('/') : "";

            string code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
            code = UrlBase64ReplaceChar(code);
            string url = emailConfirmUrl.IsMissing() ? $"{Request.Scheme}://{Request.Host}?email={user.Email}&code={code}" : $"{emailConfirmUrl}?email={user.Email}&code={code}";
            //string body =
            //    $"亲爱的用户 <strong>{user.NickName}</strong>[{user.UserName}]，您好！<br>"
            //    + $"欢迎注册，激活邮箱请 <a href=\"{url}\" target=\"_blank\"><strong>点击这里</strong></a><br>"
            //    + $"如果上面的链接无法点击，您可以复制以下地址，并粘贴到浏览器的地址栏中打开。<br>"
            //    + $"{url}<br>"
            //    + $"祝您使用愉快！";
            //await SendMailAsync(user.Email, $"{subjectPrefix}邮箱激活邮件", body);
            string bodyFormat =
                "亲爱的用户 <strong>{0}</strong>[{1}]，您好！<br>"
                + "欢迎注册，激活邮箱请 <a href=\"{2}\" target=\"_blank\"><strong>点击这里</strong></a><br>"
                + "如果上面的链接无法点击，您可以复制以下地址，并粘贴到浏览器的地址栏中打开。<br>"
                + "{2}<br>"
                + "祝您使用愉快！";
            string body = I18N.T(bodyFormat, user.NickName, user.UserName, url);
            await SendMailAsync(user.Email, I18N.T("{0} 邮箱激活邮件", subjectPrefix), body);

            return AjaxResult.Success(new { user.UserName, user.NickName, user.Email });
        }
        #endregion
        #region "发送重置密码邮件"
        /// <summary>
        /// 发送重置密码邮件
        /// </summary>
        /// <param name="user">发送的用户</param>
        /// <param name="emailConfirmUrl">邮箱确认地址</param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<AjaxResult> SendResetPasswordEmail(User user, string emailConfirmUrl)
        {
            var subjectPrefix = ServiceProvider.GetMicroCloudOptions()?.Email?.Sender?.SubjectPrefix.ReplaceNull();

            emailConfirmUrl = emailConfirmUrl.IsUrl() ? emailConfirmUrl.TrimEnd('/') : "";

            string token = await UserManager.GeneratePasswordResetTokenAsync(user);
            token = UrlBase64ReplaceChar(token);
            string url = emailConfirmUrl.IsMissing() ? $"{Request.Scheme}://{Request.Host}?email={user.Email}&token={token}" : $"{emailConfirmUrl}?email={user.Email}&token={token}";
            //string body = $"亲爱的用户 <strong>{user.NickName}</strong>[{user.UserName}]，您好！<br>"
            //    + $"欢迎使用{subjectPrefix}账户密码重置功能，请 <a href=\"{url}\" target=\"_blank\"><strong>点击这里</strong></a><br>"
            //    + $"如果上面的链接无法点击，您可以复制以下地址，并粘贴到浏览器的地址栏中打开。<br>"
            //    + $"{url}<br>"
            //    + $"祝您使用愉快！";
            //await SendMailAsync(user.Email, $"{subjectPrefix}重置密码邮件", body);
            string bodyFormat = "亲爱的用户 <strong>{0}</strong>[{1}]，您好！<br>"
                + "欢迎使用{2}账户密码重置功能，请 <a href=\"{3}\" target=\"_blank\"><strong>点击这里</strong></a><br>"
                + "如果上面的链接无法点击，您可以复制以下地址，并粘贴到浏览器的地址栏中打开。<br>"
                + "{3}<br>"
                + "祝您使用愉快！";
            string body = I18N.T(bodyFormat, user.NickName, user.UserName, subjectPrefix, url);
            await SendMailAsync(user.Email, I18N.T("{0} 重置密码邮件", subjectPrefix), body);

            return AjaxResult.Success(new { user.UserName, user.NickName, user.Email });
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="email">邮件地址</param>
        /// <param name="subject">标题</param>
        /// <param name="body">内容</param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task SendMailAsync(string email, string subject, string body)
        {
            IEmailSender sender = ServiceProvider.GetRequiredService<IEmailSender>();
            await sender.SendEmailAsync(subject, body, EmailBodyFormat.Html, email);
        }
        #endregion

        #region "验证短信验证码"
        /// <summary>
        /// 验证短信验证码
        /// </summary>
        /// <param name="bizId">发送回执ID</param>
        /// <param name="code">验证码字符串</param>
        /// <param name="phoneNumber">手机号码</param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual async Task<OperationResult> CheckSmsCode(string bizId, string code, string phoneNumber)
        {
            VerifyCodeConfirmDto dto = new()
            {
                CodeId = bizId,
                Code = code,
                PhoneNumber = phoneNumber,
            };

            var smsService = ServiceProvider.GetRequiredService<ISmsVerifyCodeService>();

            var result = await smsService.CheckAsync(dto);
            return result;
        }
        #endregion

        #region "Url Base64替换字符"
        /// <summary>
        /// Url Base64替换字符
        /// </summary>
        /// <param name="source">源</param>
        /// <returns></returns>
        [NonAction]
        [NonFunction]
        protected virtual string UrlBase64ReplaceChar(string source)
        {
            if (source.Contains('+') || source.Contains('/'))
            {
                return source.Replace('+', '-').Replace('/', '_');
            }

            if (source.Contains('-') || source.Contains('_'))
            {
                return source.Replace('-', '+').Replace('_', '/');
            }

            return source;
        }
        #endregion

        #endregion

    }
}
